package org.processmining.plugins.complianceframework;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;

import org.deckfour.xes.model.XLog;
import org.deckfour.xes.model.XTrace;
import org.processmining.framework.connections.ConnectionCannotBeObtained;
import org.processmining.framework.plugin.PluginContext;
import org.processmining.models.connections.petrinets.PNRepResultAllRequiredParamConnection;
import org.processmining.models.graphbased.directed.petrinet.elements.Transition;
import org.processmining.plugins.DataConformance.Alignment;
import org.processmining.plugins.DataConformance.ResultReplay;
import org.processmining.plugins.DataConformance.DataAlignment.PetriNet.ResultReplayPetriNetWithData;
import org.processmining.plugins.DataConformance.framework.ExecutionStep;
import org.processmining.plugins.DataConformance.visualization.DataAwareStepTypes;
import org.processmining.plugins.petrinet.replayresult.PNRepResult;
import org.processmining.plugins.petrinet.replayresult.StepTypes;
import org.processmining.plugins.replayer.replayresult.SyncReplayResult;

import de.hpi.bpt.process.petri.PetriNet;

public class ComplianceDashboard {
	private static final float violationThreshold = 0.2f;
	private static final float violationToTotalThreshold = 1.2f;

	private PNRepResult controFlowAlignment = null;
	private ResultReplayPetriNetWithData replayResultWithData = null;
	private ResultReplay replayResultTemporal = null;
	private boolean considerResources = true;
	private String resourceAttrName;
	private PluginContext context;
	private Map<String, float[]> intervals;

	public ComplianceDashboard(PNRepResult controFlowAlignment,
			ResultReplayPetriNetWithData replayResultWithData,
			ResultReplay replayResultTemporal, boolean considerResources,
			String resourceAttrName, Map<String, float[]> intervals,
			PluginContext context) {
		this.controFlowAlignment = controFlowAlignment;
		this.replayResultWithData = replayResultWithData;
		this.replayResultTemporal = replayResultTemporal;
		this.considerResources = considerResources;
		this.resourceAttrName = resourceAttrName;
		this.intervals = intervals;
		this.context = context;
		this.totalMoves = 0;
		if (this.replayResultTemporal == null)
			this.violationTypesNumber = 2;
		else
			this.violationTypesNumber = 3;

		generateCombinations();
		generateMoveMaps();
		generateActivityImportanceMap();
		generateStatements();

		int a = 5;
		int b = a + 5;
	}

	private List<List<String>> combinations = new ArrayList<List<String>>();
	List<String> attributes = new ArrayList<String>();
	// indices are as follows: 0 - Invisible moves; 1 - Sync moves; 2..n+1 -
	// moves with correct
	// combinations; n+2..2*n+1 - moves with incorrect combinations; 2*n+2 -
	// Log-only moves; 2*n+3 - Model-only moves, where n - size of the
	// combinations List

	private int totalMoves;
	private Map<String, int[]> activityMovesMap;
	private Map<String, Map<String, int[]>> attributeMovesMap;
	private Map<String, int[]> activityCaseFreqMap; // for correct: number of
													// cases, where the
													// combination for the
													// activity is correct at
													// least once; for
													// incorrect: number of
													// cases, where the
													// combination for the
													// activity is incorrect at
													// least once
	private Map<String, Map<String, int[]>> attributeCaseFreqMap;
	// map activity to array of up to 4 maps:
	// 0 - task [task_name] occurs where it shouldn't (log-only)
	// 1 - task [task_name] is skipped (model-only)
	// 2 - task [task_name] is performed with incorrect data
	// 3 - task [task_name] does not meet timing requirements
	// each map maps attribute name to another map, which in turn maps each
	// value of an attribute to the number of violations
	private int violationTypesNumber;
	// private Map<String, Map<String, Map<String, Integer>>[]>
	// activityViolationsMap;
	// Map <activity, Map<attribute, Map<value,
	// violationNumbers[violationTypesNumber]>>>
	// violationTypesNumber = 2 + number_of_attributes + 1 (2 for log only
	// moves, number_of_attributes for moves with incorrect attributes and 1 for
	// moves with incorrect time
	private Map<String, Map<String, Map<String, int[]>>> activityViolationsMap;
	private List<String> attributeIndex;

	private Map<String, Float> activityImportanceMap;
	private DefaultTreeModel statementTreeModel;

	private void generateCombinations() {
		ArrayList<String> newCombination;

		// Synchronous moves with correct
		if (this.considerResources) {
			newCombination = new ArrayList<String>();
			newCombination.add("Resource");
			combinations.add(newCombination);
		}
		if (this.replayResultTemporal != null) {
			newCombination = new ArrayList<String>();
			newCombination.add("Time");
			combinations.add(newCombination);
		}
		if (this.replayResultWithData != null) {
			for (String attrName : this.replayResultWithData.attrArray.keySet()) {
				attributes.add(attrName);
				if (attrName.equalsIgnoreCase(resourceAttrName))
					continue;
				newCombination = new ArrayList<String>();
				newCombination.add(attrName);
				combinations.add(newCombination);
			}
		}

		if (this.considerResources) {
			if (this.replayResultTemporal != null) {
				newCombination = new ArrayList<String>();
				newCombination.add("Resource");
				newCombination.add("Time");
				combinations.add(newCombination);
			}
			for (int i = 0; i < attributes.size(); i++) {
				newCombination = new ArrayList<String>();
				newCombination.add("Resource");
				newCombination.add(attributes.get(i));
				combinations.add(newCombination);
			}
		}
		if (this.replayResultTemporal != null) {
			for (int i = 0; i < attributes.size(); i++) {
				if (attributes.get(i).equalsIgnoreCase(resourceAttrName))
					continue;
				newCombination = new ArrayList<String>();
				newCombination.add("Time");
				newCombination.add(attributes.get(i));
				combinations.add(newCombination);
			}
		}

		if (attributes.size() > 1
				|| (this.considerResources && this.replayResultTemporal != null && attributes
						.size() > 0)) {
			newCombination = new ArrayList<String>();
			for (String attr : attributes) {
				if (attr.equalsIgnoreCase(resourceAttrName))
					continue;
				newCombination.add(attr);
			}
			if (this.considerResources)
				newCombination.add("Resource");
			if (this.replayResultTemporal != null)
				newCombination.add("Time");
			combinations.add(newCombination);
		}
	}

	@SuppressWarnings("unchecked")
	private void generateMoveMaps() {
		if (this.replayResultWithData != null
				&& this.replayResultWithData.attrArray.keySet().size() > 0) {
			activityMovesMap = new HashMap<String, int[]>();
			attributeMovesMap = new HashMap<String, Map<String, int[]>>();
			activityCaseFreqMap = new HashMap<String, int[]>();
			attributeCaseFreqMap = new HashMap<String, Map<String, int[]>>();
			activityViolationsMap = new HashMap<String, Map<String, Map<String, int[]>>>();
			attributeIndex = new ArrayList<String>();

			Map<String, Boolean> attrsCaseCorrect = new HashMap<String, Boolean>();
			Map<String, Boolean> attrsCaseIncorrect = new HashMap<String, Boolean>();
			Map<String, Map<String, boolean[]>> attrsCaseMoveTypes = new HashMap<String, Map<String, boolean[]>>();
			Map<String, List<String>> attrCaseValsMap = new HashMap<String, List<String>>();
			Map<String, boolean[]> activityCaseFlagMap = new HashMap<String, boolean[]>();

			for (Entry<String, float[]> activityEntry : replayResultWithData.actArray
					.entrySet()) {
				activityMovesMap.put(activityEntry.getKey(),
						new int[combinations.size() * 2 + 4]);
				activityMovesMap.get(activityEntry.getKey())[1] = (int) (activityEntry
						.getValue()[0] + activityEntry.getValue()[1]);
				activityMovesMap.get(activityEntry.getKey())[combinations
						.size() * 2 + 2] = (int) activityEntry.getValue()[2];

				activityViolationsMap.put(activityEntry.getKey(),
						new HashMap<String, Map<String, int[]>>());

				// for (int i = 0; i < activityViolationsMap.get(activityEntry
				// .getKey()).length; i++) {
				// activityViolationsMap.get(activityEntry.getKey())[i] = new
				// HashMap<String, Map<String, Integer>>();
				// }

				activityCaseFreqMap.put(activityEntry.getKey(),
						new int[combinations.size() * 2 + 4]);
				activityCaseFlagMap.put(activityEntry.getKey(),
						new boolean[combinations.size() * 2 + 4]);
			}

			for (Entry<String, float[]> attributeEntry : replayResultWithData.attrArray
					.entrySet()) {
				attributeCaseFreqMap.put(attributeEntry.getKey(),
						new HashMap<String, int[]>());
				attributeMovesMap.put(attributeEntry.getKey(),
						new HashMap<String, int[]>());
				violationTypesNumber += 1;
			}

			// iterate through data alignment. 1 alignment <=> 1 trace
			for (Alignment dataAlignment : this.replayResultWithData.labelStepArray) {
				totalMoves += dataAlignment.getLogTrace().size();
				for (String attr : this.replayResultWithData.attrArray.keySet()) {
					attrsCaseMoveTypes.put(attr,
							new HashMap<String, boolean[]>());
					attrsCaseCorrect.put(attr, true);
					attrsCaseIncorrect.put(attr, true);
					attrCaseValsMap.put(attr, new ArrayList<String>());
				}
				boolean caseTimeCorrect = true;
				boolean caseTimeIncorrect = true;
				// find time alignment of the same trace, as the current data
				// alignment
				Alignment timeAlignment = null;
				if (this.replayResultTemporal != null)
					timeAlignment = findAlignmentWithLabel(
							dataAlignment.getTraceName(),
							this.replayResultTemporal.labelStepArray);
				// correctness of the attributes within 1 step.
				Map<String, Boolean> attrCorrect = new HashMap<String, Boolean>();
				// Value assignment of the attribute within 1 step
				Map<String, String> attrValMapping = new HashMap<String, String>();
				for (int i = 0; i < dataAlignment.getProcessTrace().size(); i++) {
					ExecutionStep procStep = dataAlignment.getProcessTrace()
							.get(i);
					ExecutionStep logStep = dataAlignment.getLogTrace().get(i);
					String currentActivity;
					if (procStep.getActivity() != null)
						currentActivity = procStep.getActivity();
					else
						currentActivity = logStep.getActivity();

					// if
					// (!activityMovesMap.containsKey(procStep.getActivity()))
					// activityMovesMap.put(procStep.getActivity(),
					// new int[combinations.size() * 2 + 3]);
					//
					if (dataAlignment.getStepTypes().get(i) == DataAwareStepTypes.L) {
						activityCaseFlagMap.get(currentActivity)[combinations
								.size() * 2 + 2] |= true;
					} else if (dataAlignment.getStepTypes().get(i) == DataAwareStepTypes.MREAL) {
						activityCaseFlagMap.get(currentActivity)[combinations
								.size() * 2 + 3] |= true;
						activityMovesMap.get(currentActivity)[combinations
								.size() * 2 + 3] += 1;
					} else if (dataAlignment.getStepTypes().get(i) == DataAwareStepTypes.LMGOOD
							|| dataAlignment.getStepTypes().get(i) == DataAwareStepTypes.LMNOGOOD) {
						activityCaseFlagMap.get(currentActivity)[1] |= true;
					} else if (dataAlignment.getStepTypes().get(i) == DataAwareStepTypes.MINVI) {
						activityCaseFlagMap.get(currentActivity)[0] |= true;
						activityMovesMap.get(currentActivity)[0] += 1;
					}

					for (String attrName : this.replayResultWithData.attrArray
							.keySet()) {
						attrCorrect.put(attrName, false);
						attrValMapping.put(attrName, "");
					}
					// check correctness of time aspect
					boolean timeCorrect = false;
					if (this.replayResultTemporal != null)
						if (timeAlignment.getProcessTrace().get(i)
								.equals(timeAlignment.getLogTrace().get(i)))
							timeCorrect = true;

					caseTimeCorrect |= timeCorrect;
					caseTimeIncorrect |= (!timeCorrect);

					Set<String> attrSet = logStep.keySet();
					if ((attrSet == null || attrSet.size() == 0)
							&& (procStep.keySet() != null || procStep.keySet()
									.size() > 0)) {
						attrSet = procStep.keySet();
					}

					for (String attrName : attrSet) {
						// get current value of the attribute
						String attrVal = getIntervalIDOrValue(
								attrName,
								logStep.get(attrName) != null ? logStep.get(
										attrName).toString() : procStep.get(
										attrName).toString());
						attrValMapping.put(attrName, attrVal);
						if (!attrCaseValsMap.get(attrName).contains(attrVal))
							attrCaseValsMap.get(attrName).add(attrVal);

						if (!attributeMovesMap.get(attrName).containsKey(
								attrVal))
							attributeMovesMap.get(attrName).put(attrVal,
									new int[combinations.size() * 2 + 4]);
						if (!attrsCaseMoveTypes.get(attrName).containsKey(
								attrVal)) {
							attrsCaseMoveTypes.get(attrName).put(attrVal,
									new boolean[4]);
						}
						if (!activityViolationsMap.get(currentActivity)
								.containsKey(attrName)) {
							activityViolationsMap.get(currentActivity).put(
									attrName, new HashMap<String, int[]>());
							attributeIndex.add(attrName);
						}
						if (!activityViolationsMap.get(currentActivity)
								.get(attrName).containsKey(attrVal))
							activityViolationsMap
									.get(currentActivity)
									.get(attrName)
									.put(attrVal,
											new int[this.violationTypesNumber]);
						// if (!attributeMovesMap.get(attrName).containsKey(
						// attrVal))
						// attributeMovesMap.get(attrName).put(attrVal,
						// new int[combinations.size() * 2 + 3]);

						if (dataAlignment.getStepTypes().get(i) == DataAwareStepTypes.L) {
							attributeMovesMap.get(attrName).get(attrVal)[combinations
									.size() * 2 + 2] += 1;
							attrsCaseMoveTypes.get(attrName).get(attrVal)[2] = true;

							activityViolationsMap.get(currentActivity)
									.get(attrName).get(attrVal)[0] += 1;
						} else if (dataAlignment.getStepTypes().get(i) == DataAwareStepTypes.MREAL) {
							attributeMovesMap.get(attrName).get(attrVal)[combinations
									.size() * 2 + 3] += 1;
							attrsCaseMoveTypes.get(attrName).get(attrVal)[3] = true;
							activityViolationsMap.get(currentActivity)
									.get(attrName).get(attrVal)[1] += 1;
						} else if (dataAlignment.getStepTypes().get(i) == DataAwareStepTypes.LMGOOD
								|| dataAlignment.getStepTypes().get(i) == DataAwareStepTypes.LMNOGOOD) {
							attributeMovesMap.get(attrName).get(attrVal)[1] += 1;
							attrsCaseMoveTypes.get(attrName).get(attrVal)[1] = true;
							if (dataAlignment.getStepTypes().get(i) == DataAwareStepTypes.LMGOOD) {
								attrCorrect.put(attrName, true);
							} else {
								if (ExecutionStepsEqual(attrName, procStep,
										logStep)) {
									attrCorrect.put(attrName, true);

								} else {
									activityViolationsMap.get(currentActivity)
											.get(attrName).get(attrVal)[attributeIndex
											.indexOf(attrName)+2] += 1;
								}
							}
						} else if (dataAlignment.getStepTypes().get(i) == DataAwareStepTypes.MINVI) {
							attrsCaseMoveTypes.get(attrName).get(attrVal)[0] = true;
							attributeMovesMap.get(attrName).get(attrVal)[0] += 1;
							if (ExecutionStepsEqual(attrName, procStep, logStep)) {
								attrCorrect.put(attrName, true);
							}
						}
						if (!attrCorrect.containsKey(attrName))
							attrCorrect.put(attrName, false);

						if (attrsCaseCorrect.containsKey(attrName))
							attrsCaseCorrect.put(attrName,
									attrsCaseCorrect.get(attrName)
											| attrCorrect.get(attrName));
						else
							attrsCaseCorrect.put(attrName,
									attrCorrect.get(attrName));
						if (attrsCaseIncorrect.containsKey(attrName))
							attrsCaseIncorrect.put(attrName,
									attrsCaseIncorrect.get(attrName)
											| (!attrCorrect.get(attrName)));
						else
							attrsCaseIncorrect.put(attrName,
									!attrCorrect.get(attrName));

						if (!timeCorrect && replayResultTemporal != null) {
							activityViolationsMap.get(currentActivity)
									.get(attrName).get(attrVal)[violationTypesNumber - 1] += 1;
						}
					}

					for (int j = 0; j < combinations.size(); j++) {
						List<String> combination = combinations.get(j);
						boolean combinationCorrect = true;
						boolean combinationIncorrect = true;
						for (String element : combination) {
							if (element.equalsIgnoreCase("Time")) {
								combinationCorrect &= timeCorrect;
								combinationIncorrect &= (!timeCorrect);
							} else if (element.equalsIgnoreCase("Resource")) {
								combinationCorrect &= attrCorrect
										.get(resourceAttrName);
								combinationIncorrect &= (!attrCorrect
										.get(resourceAttrName));
								;
							} else {
								combinationCorrect &= attrCorrect.get(element)
										.booleanValue();
								combinationIncorrect &= (!attrCorrect.get(
										element).booleanValue());
							}
						}
						for (Entry<String, Map<String, int[]>> entry : attributeMovesMap
								.entrySet()) {
							if (!entry.getValue().containsKey(
									attrValMapping.get(entry.getKey())))
								continue;
							if (combinationCorrect) {
								entry.getValue().get(
										attrValMapping.get(entry.getKey()))[j + 2] += 1;
							}
							if (combinationIncorrect) {
								entry.getValue().get(
										attrValMapping.get(entry.getKey()))[combinations
										.size() + j + 2] += 1;
							}
						}
						activityCaseFlagMap.get(currentActivity)[j + 2] |= combinationCorrect;
						activityCaseFlagMap.get(currentActivity)[combinations
								.size() + j + 2] |= combinationIncorrect;
						if (combinationCorrect
								&& dataAlignment.getStepTypes().get(i) != DataAwareStepTypes.MINVI) {
							activityMovesMap.get(currentActivity)[j + 2] += 1;
						}
						if (combinationIncorrect
								&& dataAlignment.getStepTypes().get(i) != DataAwareStepTypes.MINVI) {
							activityMovesMap.get(currentActivity)[combinations
									.size() + j + 2] += 1;
						}
					}
				}

				for (Entry<String, List<String>> attrEntry : attrCaseValsMap
						.entrySet()) {
					for (String attrVal : attrEntry.getValue()) {
						if (!attributeCaseFreqMap.get(attrEntry.getKey())
								.containsKey(attrVal))
							attributeCaseFreqMap.get(attrEntry.getKey()).put(
									attrVal,
									new int[combinations.size() * 2 + 4]);
						if (attrsCaseMoveTypes.get(attrEntry.getKey()).get(
								attrVal)[0])
							attributeCaseFreqMap.get(attrEntry.getKey()).get(
									attrVal)[0] += 1;
						if (attrsCaseMoveTypes.get(attrEntry.getKey()).get(
								attrVal)[1])
							attributeCaseFreqMap.get(attrEntry.getKey()).get(
									attrVal)[1] += 1;
						if (attrsCaseMoveTypes.get(attrEntry.getKey()).get(
								attrVal)[2])
							attributeCaseFreqMap.get(attrEntry.getKey()).get(
									attrVal)[combinations.size() * 2 + 2] += 1;
						if (attrsCaseMoveTypes.get(attrEntry.getKey()).get(
								attrVal)[3])
							attributeCaseFreqMap.get(attrEntry.getKey()).get(
									attrVal)[combinations.size() * 2 + 3] += 1;
					}
				}

				for (int i = 0; i < combinations.size(); i++) {
					List<String> combination = combinations.get(i);
					boolean combinationCorrect = true;
					boolean combinationIncorrect = true;
					for (String element : combination) {
						if (element.equalsIgnoreCase("Time")) {
							combinationCorrect &= caseTimeCorrect;
							combinationIncorrect &= caseTimeIncorrect;
						} else if (element.equalsIgnoreCase("Resource")) {
							combinationCorrect &= attrsCaseCorrect
									.get(resourceAttrName);
							combinationIncorrect &= attrsCaseIncorrect
									.get(resourceAttrName);
						} else {
							combinationCorrect &= attrsCaseCorrect.get(element)
									.booleanValue();
							combinationIncorrect &= attrsCaseIncorrect.get(
									element).booleanValue();
						}
					}
					// for (Entry<String, List<String>> attrEntry :
					// attrCaseValsMap
					// .entrySet()) {
					// for (String attrVal : attrEntry.getValue())
					// if (!attributeCaseFreqMap.get(attrEntry.getKey())
					// .containsKey(attrVal))
					// attributeCaseFreqMap
					// .get(attrEntry.getKey())
					// .put(attrVal,
					// new int[combinations.size() * 2 + 4]);
					// }
					for (Entry<String, Map<String, int[]>> entry : attributeCaseFreqMap
							.entrySet()) {
						// for (String value:
						// attrCaseValsMap.get(entry.getKey()))
						// if (!entry.getValue().containsKey(value))
						// continue;
						if (combinationCorrect) {
							for (String value : attrCaseValsMap.get(entry
									.getKey())) {
								if (!entry.getValue().containsKey(value))
									entry.getValue()
											.put(value,
													new int[combinations.size() * 2 + 4]);
								entry.getValue().get(value)[i + 2] += 1;
							}
						}
						if (combinationIncorrect) {
							for (String value : attrCaseValsMap.get(entry
									.getKey())) {
								if (!entry.getValue().containsKey(value))
									entry.getValue()
											.put(value,
													new int[combinations.size() * 2 + 4]);
								entry.getValue().get(value)[combinations.size()
										+ i + 2] += 1;
							}
						}
					}
					for (Entry<String, int[]> activityEntry : activityCaseFreqMap
							.entrySet()) {
						if (activityCaseFlagMap.get(activityEntry.getKey())[i + 2])
							activityEntry.getValue()[i + 2] += 1;
						if (activityCaseFlagMap.get(activityEntry.getKey())[combinations
								.size() + i + 2])
							activityEntry.getValue()[combinations.size() + i
									+ 2] += 1;
						activityCaseFlagMap.get(activityEntry.getKey())[i + 2] = false;
						activityCaseFlagMap.get(activityEntry.getKey())[combinations
								.size() + i + 2] = false;
					}
				}
				for (Entry<String, int[]> activityEntry : activityCaseFreqMap
						.entrySet()) {
					if (activityCaseFlagMap.get(activityEntry.getKey())[0])
						activityEntry.getValue()[0] += 1;
					if (activityCaseFlagMap.get(activityEntry.getKey())[1])
						activityEntry.getValue()[1] += 1;
					if (activityCaseFlagMap.get(activityEntry.getKey())[combinations
							.size() * 2 + 2])
						activityEntry.getValue()[combinations.size() * 2 + 2] += 1;
					if (activityCaseFlagMap.get(activityEntry.getKey())[combinations
							.size() * 2 + 3])
						activityEntry.getValue()[combinations.size() * 2 + 3] += 1;
					activityCaseFlagMap.get(activityEntry.getKey())[0] = false;
					activityCaseFlagMap.get(activityEntry.getKey())[1] = false;
					activityCaseFlagMap.get(activityEntry.getKey())[combinations
							.size() * 2 + 2] = false;
					activityCaseFlagMap.get(activityEntry.getKey())[combinations
							.size() * 2 + 3] = false;
				}

				for (Entry<String, List<String>> attrEntry : attrCaseValsMap
						.entrySet()) {
					for (String attrValue : attrEntry.getValue()) {
						if (!attributeCaseFreqMap.get(attrEntry.getKey())
								.containsKey(attrValue))
							attributeCaseFreqMap.get(attrEntry.getKey()).put(
									attrValue,
									new int[combinations.size() * 2 + 4]);
						// attributeCaseFreqMap.get(attrEntry.getKey()).get(attrValue)[]
					}
				}
			}
		} else {
			activityMovesMap = new HashMap<String, int[]>();
			activityCaseFreqMap = new HashMap<String, int[]>();
			Map<String, boolean[]> activityCaseFlagMap = new HashMap<String, boolean[]>();
			List<String> caseIDs = getCaseIdsFromReplay();

			for (SyncReplayResult replay : controFlowAlignment) {
				int caseIDSize = replay.getTraceIndex().size();
				totalMoves += replay.getNodeInstance().size() * caseIDSize;

				int numberWithCorrectTime = 0;
				for (int i = 0; i < replay.getNodeInstance().size(); i++) {
					String activityName = getActivityName(replay
							.getNodeInstance().get(i));
					if (!activityMovesMap.containsKey(activityName)) {
						activityMovesMap.put(activityName,
								new int[combinations.size() * 2 + 3]);
					}
					if (!activityCaseFreqMap.containsKey(activityName)) {
						activityCaseFreqMap.put(activityName,
								new int[combinations.size() * 2 + 3]);
						activityCaseFlagMap.put(activityName,
								new boolean[combinations.size() * 2 + 3]);
					}

					if (replay.getStepTypes().get(i) == StepTypes.L) {
						activityMovesMap.get(activityName)[combinations.size() * 2 + 2] += caseIDSize;
						activityCaseFlagMap.get(activityName)[combinations
								.size() * 2 + 2] |= true;
					} else if (replay.getStepTypes().get(i) == StepTypes.MREAL) {
						activityMovesMap.get(activityName)[combinations.size() * 2 + 3] += caseIDSize;
						activityCaseFlagMap.get(activityName)[combinations
								.size() * 2 + 3] |= true;
					} else if (replay.getStepTypes().get(i) == StepTypes.MINVI) {
						activityMovesMap.get(activityName)[0] += caseIDSize;
						activityCaseFlagMap.get(activityName)[0] |= true;
					} else if (replay.getStepTypes().get(i) == StepTypes.LMGOOD) {
						activityMovesMap.get(activityName)[1] += caseIDSize;
						activityCaseFlagMap.get(activityName)[1] |= true;
					}
					if (this.replayResultTemporal != null) {
						numberWithCorrectTime = 0;
						for (int j = 0; j < caseIDSize; j++) {
							Alignment timeAlignment = findAlignmentWithLabel(
									caseIDs.get(j),
									this.replayResultTemporal.labelStepArray);
							if (timeAlignment.getStepTypes().get(i) == DataAwareStepTypes.LMGOOD)
								numberWithCorrectTime += 1;
						}
						activityMovesMap.get(activityName)[2] += numberWithCorrectTime;
						activityMovesMap.get(activityName)[3] += (caseIDSize - numberWithCorrectTime);
					}
				}

				for (Entry<String, boolean[]> activityCaseFlagMapEntry : activityCaseFlagMap
						.entrySet()) {
					for (int i = 0; i < activityCaseFlagMapEntry.getValue().length; i++) {
						if (activityCaseFlagMapEntry.getValue()[i])
							activityCaseFreqMap.get(activityCaseFlagMapEntry
									.getKey())[i] += caseIDSize;
						activityCaseFlagMapEntry.getValue()[i] = false;
					}
					if (this.replayResultTemporal != null) {
						activityCaseFreqMap.get(activityCaseFlagMapEntry
								.getKey())[1] += numberWithCorrectTime;
						activityCaseFreqMap.get(activityCaseFlagMapEntry
								.getKey())[2] += (caseIDSize - numberWithCorrectTime);
					}
				}
			}

		}
	}

	private void generateActivityImportanceMap() {
		// TODO: ask user
		activityImportanceMap = new HashMap<String, Float>();
		for (String activity : activityMovesMap.keySet()) {
			activityImportanceMap.put(activity, 1.0f);
		}
	}

	private void generateStatements() {
		// TODO: Think of ranking based on case stats
		// TODO: leave nodes with no children, if the rank score is high enough?

		DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(
				"Root Node");
		statementTreeModel = new DefaultTreeModel(rootNode);
		Map<TreeNode, Float> rankScoreMap = new HashMap<TreeNode, Float>();
		List<DefaultMutableTreeNode> nodeCandidates = new ArrayList<DefaultMutableTreeNode>();
		if (this.replayResultWithData != null) {
			for (Entry<String, int[]> activityEntry : activityMovesMap
					.entrySet()) {
				float activityImportance = activityImportanceMap.get(
						activityEntry.getKey()).floatValue();
				DefaultMutableTreeNode activityNode = new DefaultMutableTreeNode(
						activityEntry.getKey());
				nodeCandidates.add(activityNode);
				// total number of moves with incorrect assignment + total
				// number of log-only moves + moves with incorrect time
				int totalActivityViolations = (int) (this.replayResultWithData.actArray
						.get(activityEntry.getKey())[1] + this.replayResultWithData.actArray
						.get(activityEntry.getKey())[2]);
				if (this.replayResultTemporal != null)
					totalActivityViolations += this.replayResultTemporal.actArray
							.get(activityEntry.getKey())[1];
				rankScoreMap.put(activityNode, activityImportance
						* (float) totalActivityViolations);
				int[] activityMoveStats = activityMovesMap.get(activityEntry
						.getKey());
				int totalActivityMoves = activityMoveStats[0]
						+ activityMoveStats[1]
						+ activityMoveStats[combinations.size() * 2 + 2];

				// moves on log
				if (activityImportance
						* ((float) activityMoveStats[combinations.size() * 2 + 2] / (float) totalActivityMoves) > violationThreshold) {
					DefaultMutableTreeNode violationNode = new DefaultMutableTreeNode(
							generateViolationSentence(activityEntry.getKey(), 0));
					activityNode.add(violationNode);
					findAttributeAssigments(0, activityEntry.getKey(),
							totalActivityViolations, violationNode);
				}
				// moves on model
				if (activityImportance
						* (float) activityMoveStats[combinations.size() * 2 + 3]
						/ (float) totalActivityMoves > violationThreshold) {
					DefaultMutableTreeNode violationNode = new DefaultMutableTreeNode(
							generateViolationSentence(activityEntry.getKey(), 1));
					activityNode.add(violationNode);
					findAttributeAssigments(1, activityEntry.getKey(),
							totalActivityViolations, violationNode);
				}
				// moves with incorrect data
				if (activityImportance
						* replayResultWithData.actArray.get(activityEntry
								.getKey())[1] / (float) totalActivityMoves > violationThreshold) {
					for (int i=2;i<violationTypesNumber;i++){
					DefaultMutableTreeNode violationNode = new DefaultMutableTreeNode(
							generateViolationSentence(activityEntry.getKey(), i));
					activityNode.add(violationNode);
					findAttributeAssigments(i, activityEntry.getKey(),
							totalActivityViolations, violationNode);
					}
				}
				// moves with incorrect time
				if (this.replayResultTemporal != null) {
					// 0. find number of time violations
					int timeViolations = 0;
					for (int i = 0; i < this.combinations.size(); i++) {
						if (combinations.get(i).size() == 1
								&& combinations.get(i).get(0).equals("Time")) {
							timeViolations = activityMoveStats[2
									+ combinations.size() + i];
							break;
						}
					}
					// 1. check threshold and add node
					if (activityImportance * (float) timeViolations
							/ (float) totalActivityMoves > violationThreshold) {
						DefaultMutableTreeNode violationNode = new DefaultMutableTreeNode(
								generateViolationSentence(
										activityEntry.getKey(), 2));
						statementTreeModel.insertNodeInto(violationNode,
								activityNode, activityNode.getChildCount());
						findAttributeAssigments(violationTypesNumber-1, activityEntry.getKey(),
								totalActivityViolations, violationNode);
					}
				}
			}

			for (Entry<String, Map<String, int[]>> attributeEntry : attributeMovesMap
					.entrySet()) {
				for (Entry<String, int[]> attributeValueEntry : attributeEntry
						.getValue().entrySet()) {
					int[] attrValueStats = this.attributeMovesMap.get(
							attributeEntry.getKey()).get(
							attributeValueEntry.getKey());
					int totalValueMoves = attrValueStats[0] + attrValueStats[1]
							+ attrValueStats[combinations.size() * 2 + 2];
					int totalValueViolations = getTotalValueViolations(
							attributeEntry.getKey(),
							attributeValueEntry.getKey());
					float valueViolationRankScore = getValueViolationRankScore(
							attributeEntry.getKey(),
							attributeValueEntry.getKey());
					if ((float) totalValueViolations / (float) totalValueMoves < violationThreshold)
						continue;
					DefaultMutableTreeNode assignmentNode = new DefaultMutableTreeNode(
							"When "
									+ generateAttrAssignmentSentence(
											attributeEntry.getKey(),
											attributeValueEntry.getKey()));
					rankScoreMap.put(assignmentNode, valueViolationRankScore);
					for (int i = 0; i < violationTypesNumber; i++) {
						findActivitiesWithAssigments(i,
								attributeEntry.getKey(),
								attributeValueEntry.getKey(), totalValueMoves,
								assignmentNode);
					}
					nodeCandidates.add(assignmentNode);
				}
			}
			// float[] nodeRankScores = new float[nodeCandidates.size()];
			for (int i = 0; i < nodeCandidates.size(); i++) {
				if (nodeCandidates.get(i).isLeaf()) {
					nodeCandidates.remove(i);
					i -= 1;
				}
				// else{
				// nodeRankScores[i] =
				// rankScoreMap.get(nodeCandidates.get(i)).floatValue();
				// }
			}

			while (true) {
				boolean sorted = true;
				for (int i = 0; i < nodeCandidates.size() - 1; i++) {
					if (rankScoreMap.get(nodeCandidates.get(i)).floatValue() < rankScoreMap
							.get(nodeCandidates.get(i + 1)).floatValue()) {
						DefaultMutableTreeNode buf = nodeCandidates.get(i + 1);
						nodeCandidates.set(i + 1, nodeCandidates.get(i));
						nodeCandidates.set(i, buf);
						sorted = false;
					}
				}
				if (sorted)
					break;
			}
			for (DefaultMutableTreeNode node : nodeCandidates) {
				rootNode.add(node);
			}
		} else {
			// TODO: implement for absence of dataAlignment
		}
	}

	private float getValueViolationRankScore(String attribute, String value) {
		float result = 0;
		for (Entry<String, Map<String, Map<String, int[]>>> activityViolationsEntry : activityViolationsMap
				.entrySet()) {

			for (int i = 0; i < violationTypesNumber; i++) {
				if (!activityViolationsEntry.getValue().containsKey(attribute))
					continue;
				if (!activityViolationsEntry.getValue().get(attribute)
						.containsKey(value))
					continue;
				result += (float) activityViolationsEntry.getValue()
						.get(attribute).get(value)[i]
						* activityImportanceMap.get(
								activityViolationsEntry.getKey()).floatValue();
			}
		}

		return result;
	}

	private int getTotalValueViolations(String attribute, String value) {
		int result = 0;
		for (Entry<String, Map<String, Map<String, int[]>>> activityViolationsEntry : activityViolationsMap
				.entrySet()) {

			for (int i = 0; i < violationTypesNumber; i++) {
				if (!activityViolationsEntry.getValue().containsKey(attribute))
					continue;
				if (!activityViolationsEntry.getValue().get(attribute)
						.containsKey(value))
					continue;
				result += activityViolationsEntry.getValue().get(attribute)
						.get(value)[i];
			}
		}

		return result;
	}

	private void findActivitiesWithAssigments(int violationType,
			String attribute, String value, int totalValueOccur,
			DefaultMutableTreeNode violationNode) {
		// TODO: calculate rank
		float valueTotalOccurRatio = (float) totalValueOccur
				/ (float) this.totalMoves;
		for (Entry<String, Map<String, Map<String, int[]>>> activityViolationsEntry : activityViolationsMap
				.entrySet()) {
			int totalActivityViolations = (int) (this.replayResultWithData.actArray
					.get(activityViolationsEntry.getKey())[1] + this.replayResultWithData.actArray
					.get(activityViolationsEntry.getKey())[2]);
			if (this.replayResultTemporal != null)
				totalActivityViolations += this.replayResultTemporal.actArray
						.get(activityViolationsEntry.getKey())[1];
			if (!activityViolationsEntry.getValue().containsKey(attribute)
					|| !activityViolationsEntry.getValue().get(attribute)
							.containsKey(value))
				continue;
			float valueViolatingOccurRatio = (float) activityViolationsEntry
					.getValue().get(attribute).get(value)[violationType]
					/ (float) totalActivityViolations;
			if (valueViolatingOccurRatio / valueTotalOccurRatio >= violationToTotalThreshold) {
				DefaultMutableTreeNode attrAssignmentNode = new DefaultMutableTreeNode(
						generateViolationSentence(
								activityViolationsEntry.getKey(), violationType));
				violationNode.add(attrAssignmentNode);
			}
		}
	}

	private void findAttributeAssigments(int violationType,
			String activityName, int totalActivityViolations,
			DefaultMutableTreeNode violationNode) {
		// TODO: think of ranking (later)
		for (Entry<String, Map<String, int[]>> activityViolationEntry : this.activityViolationsMap
				.get(activityName).entrySet()) {
			for (Entry<String, int[]> attrViolatingValueEntry : activityViolationEntry
					.getValue().entrySet()) {
				int[] attrValueStats = this.attributeMovesMap.get(
						activityViolationEntry.getKey()).get(
						attrViolatingValueEntry.getKey());
				float valueTotalOccurRatio = (float) (attrValueStats[0]
						+ attrValueStats[1] + attrValueStats[this.combinations
						.size() * 2 + 2]) / (float) this.totalMoves;

				float valueViolatingOccurRatio = (float) attrViolatingValueEntry
						.getValue()[violationType]
						/ (float) totalActivityViolations;

				if (valueViolatingOccurRatio / valueTotalOccurRatio >= violationToTotalThreshold) {
					DefaultMutableTreeNode attrAssignmentNode = new DefaultMutableTreeNode(
							generateSentence(
									violationNode.getUserObject().toString(),
									generateAttrAssignmentSentence(
											activityViolationEntry.getKey(),
											attrViolatingValueEntry.getKey())));
					violationNode.add(attrAssignmentNode);
				}
			}
		}
	}

	private String generateSentence(String violation, String attrAssignment) {
		return violation + " when " + attrAssignment;
	}

	private String generateAttrAssignmentSentence(String attrName,
			String attrValue) {
		return generateAttrAssignmentSentence(attrName, attrValue, null, null);
	}

	private String generateAttrAssignmentSentence(String attrName,
			String attrValue, String attrAssignment, String separator) {
		attrValue = getIntervalByIDOrValue(attrName, attrValue);
		String result;
		if (this.intervals.containsKey(attrName))
			result = attrName + " in " + attrValue;
		else
			result = attrName + " = " + attrValue;
		if (attrAssignment != null) {
			result += separator + attrAssignment;
		}
		return result;
	}

	private String generateViolationSentence(String activityName,
			int violationType) {
		return generateViolationSentence(activityName, violationType, null,
				null);
	}

	private String generateViolationSentence(String activityName,
			int violationType, String violation, String separator) {
		String result = null;
		if (violationType == 0)
			result = "Activity " + activityName + " occurs where it shouldn't";
		else if (violationType == 1)
			result = "Activity " + activityName + " is skipped";
		else if (violationType == (violationTypesNumber - 1)
				&& replayResultTemporal != null)
			result = "Activity " + activityName
					+ " does not meet timing requirements";
		else
			result = "Activity " + activityName
					+ " is performed with incorrect "
					+ attributeIndex.get(violationType - 2);
		if (violation != null)
			result += separator + violation;

		return result;
	}

	private String getActivityName(Object nodeInstance) {
		String activityName;
		if (nodeInstance instanceof Transition) {
			activityName = ((Transition) nodeInstance).getLabel();
		} else if (nodeInstance instanceof String) {
			activityName = (String) nodeInstance;
		} else {
			activityName = nodeInstance.toString();
		}

		activityName = activityName.replace("+comlete", "");
		activityName = activityName.replace("+Comlete", "");
		return activityName;
	}

	private Alignment findAlignmentWithLabel(String label,
			Collection<Alignment> labelStepArray) {
		Alignment result = null;
		for (Alignment alignment : labelStepArray)
			if (alignment.getTraceName().equalsIgnoreCase(label)) {
				result = alignment;
				break;
			}
		return result;
	}

	private boolean ExecutionStepsEqual(String attribute, ExecutionStep step1,
			ExecutionStep step2) {
		float epsilon = 0.00005F;

		if (step1.getActivity() == null) {
			return (step2.getActivity() == null);
		} else {
			if (step1.getActivity().equals(step2.getActivity())) {
				Object otherValue = step2.get(attribute);
				Object thisValue = step1.get(attribute);
				if (otherValue == null && thisValue == null)
					return true;
				else if (otherValue == null || thisValue == null)
					return false;
				else if (thisValue instanceof Number
						|| otherValue instanceof Number) {
					double thisNumber;
					if (thisValue instanceof Number)
						thisNumber = ((Number) thisValue).doubleValue();
					else if (thisValue instanceof String)
						try {
							thisNumber = Double.parseDouble((String) thisValue);
						} catch (NumberFormatException e) {
							return false;
						}
					else
						return false;
					double otherNumber;
					if (otherValue instanceof Number)
						otherNumber = ((Number) otherValue).doubleValue();
					else if (otherValue instanceof String) {
						try {
							otherNumber = Double
									.parseDouble((String) otherValue);
						} catch (NumberFormatException e) {
							return false;
						}
					} else
						return false;
					if (thisNumber < otherNumber * (1 - epsilon)
							|| thisNumber > otherNumber * (1 + epsilon))
						return false;
				} else {
					if (!otherValue.equals(thisValue))
						return false;
				}
				return true;
			} else
				return false;
		}
	}

	private List<String> getCaseIdsFromReplay() {
		PNRepResultAllRequiredParamConnection conn;
		List<String> result = new ArrayList<String>();
		try {
			conn = context.getConnectionManager().getFirstConnection(
					PNRepResultAllRequiredParamConnection.class, context,
					controFlowAlignment);

			PetriNet net = conn
					.getObjectWithRole(PNRepResultAllRequiredParamConnection.PN);
			XLog log = conn
					.getObjectWithRole(PNRepResultAllRequiredParamConnection.LOG);
			for (XTrace trace : log) {
				result.add(trace.getAttributes().get("concept:name").toString());
			}
			return result;
			// map =
			// conn.getObjectWithRole(PNRepResultAllRequiredParamConnection.TRANS2EVCLASSMAPPING);
		} catch (ConnectionCannotBeObtained e) {
			e.printStackTrace();
			return null;
		}
	}

	public String getIntervalIDOrValue(String attrName, String attrValue) {
		float[] attrIntervals = this.intervals.get(attrName);
		if (attrIntervals != null) {
			float value = Float.parseFloat(attrValue);
			int intervalNum = 0;
			for (int i = 0; i < attrIntervals.length; i++) {
				if (value <= attrIntervals[i]) {
					intervalNum = i;
					break;
				}
			}
			return Integer.toString(intervalNum);
		} else
			return attrValue;
	}

	public String getIntervalByIDOrValue(String attrName, String attrValue) {
		String result = attrValue;
		float[] attrIntervals = this.intervals.get(attrName);
		if (attrIntervals != null) {
			int index = Integer.parseInt(attrValue);
			if (index == 0)
				result = "(-inf;" + attrIntervals[index] + "]";
			else
				result = "[" + attrIntervals[index - 1] + ";"
						+ attrIntervals[index] + "]";
		}
		return result;
	}

	// accessors
	public PNRepResult getControFlowAlignment() {
		return this.controFlowAlignment;
	}

	public ResultReplayPetriNetWithData getReplayResultWithData() {
		return this.replayResultWithData;
	}

	public ResultReplay getReplayResultTemporal() {
		return this.replayResultTemporal;
	}

	public boolean getConsiderResources() {
		return this.considerResources;
	}

	public String getResourceAttrName() {
		return this.resourceAttrName;
	}

	public Map<String, float[]> getIntervals() {
		return this.intervals;
	}

	public Map<String, int[]> getActivityMovesMap() {
		return this.activityMovesMap;
	}

	public Map<String, Map<String, int[]>> getAttributeMovesMap() {
		return this.attributeMovesMap;
	}

	public Map<String, int[]> getActivityCaseFreqMap() {
		return this.activityCaseFreqMap;
	}

	public Map<String, Map<String, int[]>> getAttributeCaseFreqMap() {
		return this.attributeCaseFreqMap;
	}

	public List<List<String>> getCombinations() {
		return this.combinations;
	}

	public DefaultTreeModel getStatementTreeModel() {
		return this.statementTreeModel;
	}
}